/*******************************************************************************
 * Copyright 2010 DemandSpot
 * 
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *   http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 ******************************************************************************/
package com.demandspot.aws;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Collections;
import java.util.List;
import java.util.Properties;

import com.xerox.amazonws.sdb.Domain;
import com.xerox.amazonws.sdb.Item;
import com.xerox.amazonws.sdb.ItemAttribute;
import com.xerox.amazonws.sdb.ListDomainsResult;
import com.xerox.amazonws.sdb.QueryResult;
import com.xerox.amazonws.sdb.QueryWithAttributesResult;
import com.xerox.amazonws.sdb.SDBException;
import com.xerox.amazonws.sdb.SimpleDB;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;


public class AWSSDBService {
	private SimpleDB sdb;

	public AWSSDBService() throws FileNotFoundException, IOException {		
		this(AWSSDBService.class.getResourceAsStream("/conf/aws.properties"));
	}

	public AWSSDBService(InputStream propInputStream) throws IOException {
		Properties props = new Properties();
		props.load(propInputStream);
		init(props);
	}
	
	public AWSSDBService(File propsFile) throws FileNotFoundException, IOException {
		Properties props = new Properties();
		props.load(new FileInputStream(propsFile));
		init(props);
	}
	
	public AWSSDBService(URL propsURL) throws FileNotFoundException, IOException {
		this(new File(propsURL.getFile()));
	}
	
	public AWSSDBService(Properties props) {
		init(props);
	}

	public AWSSDBService(String accessKey, String secretKey) {
		Properties props = new Properties();
		props.put(AWSConsts.AWS_ACCESS_KEY, accessKey);
		props.put(AWSConsts.AWS_SECRET_KEY, secretKey);
		init(props);
	}
	
	private void init(Properties props) {
		String accessKey = props.getProperty(AWSConsts.AWS_ACCESS_KEY);
		String secretKey = props.getProperty(AWSConsts.AWS_SECRET_KEY);
		
		accessKey = System.getProperty(AWSConsts.AWS_ACCESS_KEY, accessKey);
		secretKey = System.getProperty(AWSConsts.AWS_SECRET_KEY, secretKey);
				
		sdb = new SimpleDB(accessKey, secretKey, true);
		
	}
	
	public Domain createDomain(String name) throws SDBException {		
		return sdb.createDomain(name);
	}
	
	public void deleteDomain(Domain domain) throws SDBException {
		sdb.deleteDomain(domain);
	}
	
	public Domain getDomain(String domainName) throws SDBException {
		return sdb.getDomain(domainName);
	}

	public Domain getOrCreateDomain(String domainName) throws SDBException {
		Domain domain = sdb.getDomain(domainName);
		Item testItem;
		try {			
			String testItemName = "test_item_" + System.currentTimeMillis();
			testItem = domain.getItem(testItemName);
			testItem.putAttributes(Collections.singletonList(new ItemAttribute("x", "y", true)));
		} catch (SDBException ex) {
			//if we failed it probably means the domain does not exist, then we create a new one
			return createDomain(domainName);
		}
		//the domain exists so clean up the test
		domain.deleteItem(testItem.getIdentifier());
		return domain;		
	}
	
	public List<Domain> listDomains() throws SDBException {
		ListDomainsResult res = sdb.listDomains();
		return res.getDomainList();
	}
	
	public Item createItem(Domain domain, String key, List<ItemAttribute> attributes) throws SDBException {
		Item it = domain.getItem(key);
		it.putAttributes(attributes);
		return it;
	}

	public void addItemAttribute(Domain domain, String itemId, String key, String value) throws SDBException {
		Item it = domain.getItem(itemId);
		it.putAttributes(Collections.singletonList(new ItemAttribute(key, value, true)));
	}

	/*
	 * Retrieves all the items in the specified SimpleDB domain, along with their attributes.
	 * Polls SimpleDB repeatedly until all items in the domain are retrieved.
	 * @return a Map of item identifiers to a list of ItemAttribute's
	 */
	public Map<String, List<ItemAttribute>> getAllItemsWithAttributes(Domain domain) throws SDBException {
		Map<String, List<ItemAttribute>> allItems = new HashMap<String, List<ItemAttribute>>();
		double boxUsage = 0.0;
		QueryWithAttributesResult res;
		String nextToken = null;
		do {
			res = domain.listItemsWithAttributes(null, null, nextToken, 250);
			boxUsage += Double.parseDouble(res.getBoxUsage());
			nextToken = res.getNextToken();
			allItems.putAll(res.getItems());
		} while (StringUtils.isNotBlank(nextToken));
		Logger logger = Logger.getLogger(AWSSDBService.class);
		logger.debug("Total items retrieved: " + allItems.size());
		logger.debug("Total box usage:" + boxUsage);
		return allItems;
	}

	/*
	 * List all items within a domain.
	 * Polls SimpleDB repeatedly until all items in the domain are retrieved.
	 * @return a List of item identifiers.
	 */
	public List<String> listAllItems(Domain domain) throws SDBException {
		double boxUsage = 0.0;
		List<String> itemNames = new ArrayList<String>();
		QueryResult res;
		String nextToken = null;
		do {
			res = domain.listItems(null, nextToken, 250);
			boxUsage += Double.parseDouble(res.getBoxUsage());
			nextToken = res.getNextToken();
			List<Item> items = res.getItemList();
			for (Item item : items)
				itemNames.add(item.getIdentifier());
		} while (StringUtils.isNotBlank(nextToken));
		Logger logger = Logger.getLogger(AWSSDBService.class);
		logger.debug("Total items returned: " + itemNames.size());
		logger.debug("Total box usage:" + boxUsage);
		return itemNames;
	}

}
